package escalonamento;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import model.Processo;

/**
 *
 * @author João Gonçalves
 * @author Eventon Silva
 */
public class EscalonamentoRR implements InterfaceEscalonamento {

    /**
     * Método responsável pelo escalonamento Round-Robin (RR) e pela impressão
     * dos dados sobre o mesmo.
     *
     * @param processos Lista de processos
     */
    @Override
    public void escalonamento(ArrayList<Processo> processos) {
        
        // Tempo decorrido durante o processamento
        int tempoDecorrido = 0;
        // Tempo médio de execução
        double tempoMedioExecucao = 0;
        // Quantidade de trocas de contextos
        int trocaContexto = 0;
        // Tempo médio de espera de um processo
        double tempoMedioEspera = 0;
        // Quantum do escalonamento
        int quantum = 0;
        // Quantidade de processos
        int quantProcessos = processos.size();
        // Processo em execução
        Processo processoExecucao = null;

        /*
         *  Lista com os processos em execução e o com
         *  os dados recolhidos, respectivamente.
         */
        Queue<Processo> listaProcesso = new LinkedList<>();
        Queue<Processo> listaProcessoDados = new LinkedList<>();

        System.out.printf("tempo ");

        /*
         * Enumera os processos de P1 a PN, onde N
         * é o número total de processos.
         */
        for (int i = 1; i <= quantProcessos; i++) {
            System.out.printf("P%2s%2s", String.valueOf(i), "");
        }

        // Quebra de linha
        System.out.println();

        /*
         * Loop principal, responsável por processar 
         * todos os dados até a obtenção das informações
         * necessária para apresentar os dados sobre os processos.
         */
        while (listaProcessoDados.size() != processos.size()) {

            /*
             * Se o processo entra no instante X, então
             * ele será adicionado a fila de processos
             * para execução
             */
            for (Processo processo : processos) {
                if (processo.getDataCriacao() == tempoDecorrido) {
                    listaProcesso.add(processo);
                }
            }

            /*
             * Se nenhum processo estiver em execução, 
             * irá ser colocado o topo da fila de processos.
             */
            if ((!listaProcesso.isEmpty()) && (listaProcesso.peek().getStatus() == 0)) {

                processoExecucao = listaProcesso.peek();
                processoExecucao.setStatus(1);
                quantum = 2;
                processoExecucao.tempo();
                quantum--;
                trocaContexto++; // Nova troca de contexto
            }

            // Imprime o tempo passando no decorrer do escalonamento
            System.out.printf("%2s-%2s ", String.valueOf(tempoDecorrido), (tempoDecorrido + 1));

            for (int i = 0; i < quantProcessos; i++) {

                if (!listaProcesso.contains(processos.get(i))) {
                    System.out.printf("%2s%3s", " ", "");
                } else {

                    if (processos.get(i).equals(processoExecucao)) {
                        System.out.printf("%2s%3s", "#", "");
                    } else {
                        System.out.printf("%2s%3s", "-", "");
                    }
                }
            }

            //Quebra de linha
            System.out.println();

            /*
             * Verifica em qual situção o processo se encaixa,
             * com relação a quanto tempo resta de execução,
             * se já morreu ou se acabou seu quantum e deve ir ao
             * final da lista.
             */
            if (processoExecucao != null && quantum == 0) {
                listaProcesso.peek().setStatus(0);
                if (processoExecucao.getDuracaoRestante() == 0) {
                    processoExecucao = null;
                    listaProcesso.peek().setTermino(tempoDecorrido + 1);
                    listaProcessoDados.add(listaProcesso.poll());

                } else {
                    listaProcesso.add(listaProcesso.poll());
                }
            } else if (processoExecucao != null && processoExecucao.getDuracaoRestante() > 0) {
                processoExecucao.tempo();
            } else if (processoExecucao != null && processoExecucao.getDuracaoRestante() == 0) {
                processoExecucao = null;
                listaProcesso.peek().setTermino(tempoDecorrido + 1);
                listaProcessoDados.add(listaProcesso.poll());

            }
            // Contabiliza o tempo
            quantum--;
            tempoDecorrido += 1;
        }

        /*
         * Calcula o somatório dos tempos gastos na
         * execução e espera pelos processos.
         */
        for (Processo processo : listaProcessoDados) {
            tempoMedioExecucao += processo.getTermino() - processo.getDataCriacao();
            tempoMedioEspera += processo.getTermino() - processo.getDuracao() - processo.getDataCriacao();
        }

        /*
         * Calcula os tempos médios de execução e espera de acordo
         * com a quantidade de processos.
         */
        tempoMedioExecucao = tempoMedioExecucao / quantProcessos;
        tempoMedioEspera = tempoMedioEspera / quantProcessos;

        System.out.println("\nTempo médio execução: " + tempoMedioExecucao + "s");
        System.out.println("Tempo médio de espera: " + tempoMedioEspera + "s");
        System.out.println("Número de trocas de contexto: " + trocaContexto);
    }
}